package org.wbd.pklib.db.logic;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.wbd.pklib.db.*;

import com.sun.org.apache.xerces.internal.impl.dv.xs.DayDV;

/**
 * Singleton obslugujacy aparat sugestii nastepnych ksiazek do przeczytania
 */
public class SuggestionLogic {
	
	
	static private SuggestionLogic  suggestionLogic = null;
	
	private SessionFactory sessionFactory;
	private Double[] connFactor;
	private List<Object> repoBooks;
	

		private	Double[][] weights ={ 
				{1., 0.1, 0.3, 0., 0.1, 0.6, 0., 0.2, 0., 0., 0., 0., 0.2, 0., 0.},
				{0.1, 1., 0.7, 0., 0.1, 0., 0.4, 0.2, 0.2, 0.2, 0.8, 0., 0.2, 0., 0.},
				{0.3, 0.7, 1., 0., 0.3, 0., 0.4, 0., 0.1, 0.1, 0.3, 0., 0.2, 0., 0.},
				{0., 0., 0., 1., 0., 0., 0., 0.1, 0.7, 0.7, 0., 0., 0., 0., 0.},
				{0.1, 0.1, 0.3, 0., 1., 0., 0.5, 0.1, 0., 0., 0.2, 0., 0.1, 0.1, 0.},
				{0.6, 0., 0., 0., 0., 1., 0., 0.3, 0., 0., 0., 0., 0., 0., 0.},
				{0., 0.4, 0.4, 0., 0.5, 0., 1., 0.1, 0., 0., 0.2, 0.2, 0.2, 0., 0.},
				{0.2, 0.2, 0., 0.1, 0.1, 0.3, 0.1, 1., 0.1, 0.1, 0., 0., 0., 0., 0.},
				{0., 0.2, 0.1, 0.7, 0., 0., 0., 0.1, 1., 0.9, 0., 0., 0., 0., 0.},
				{0., 0.2, 0.1, 0.7, 0., 0., 0., 0.1, 0.9, 1., 0., 0., 0., 0., 0.},
				{0., 0.8, 0.3, 0., 0.2, 0., 0.2, 0., 0., 0., 1., 0., 0., 0., 0.7},
				{0., 0., 0., 0., 0., 0., 0.2, 0., 0., 0., 0., 1., 0.5, 0.5, 0.3},
				{0.2, 0.2, 0.2, 0., 0.1, 0., 0.2, 0., 0., 0., 0., 0.5, 1., 0.6, 0.3},
				{0., 0., 0., 0., 0.1, 0., 0., 0., 0., 0., 0., 0.5, 0.6, 1., 0.5},
				{0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.7, 0.3, 0.3, 0.5, 1.} //Literatura naukowa
	};
	
	String[] category = {"Dramat", "Fantasy", "Horror", "Komedia", "Kryminał", "Okruchy zycia", "Przygodowa", "Satyra", "Parodia", "Romans", "Science fiction", "Sport", "Militarystyka", "Literatura Faktu", "Literatura Naukowa"};
	Map<String , Integer> categoryToNum ;;
	
	private SuggestionLogic(){
		sessionFactory = DataBaseSession.getInstance().getSessionFactory();
		categoryToNum = new HashMap<String, Integer>();
		for (int i = 0; i<weights[0].length; ++i){
			for (int j=0; j<weights[0].length; ++j){
				weights[i][j] = 1 - weights[i][j];
			}
		}
		
		for (int i = 0 ; i<category.length; ++i){
			categoryToNum.put(category[i], i);			
		}
		
	};
	
	/**
	 * Zwraca instancje SuggestionLogic
	 */
	static public SuggestionLogic getInstance(){
		if (suggestionLogic == null){
			suggestionLogic = new SuggestionLogic();
		}
		return suggestionLogic;
	}

	/**
	 * Zwraca zbior ksiazek z danego repozytorium przeczytanych przez uzytkownika
	 */
	public Set<Book> getReadBooks(User user, Repo repo){
		
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		
		List<Object> userLoans = LoanLogic.getInstance().getFinishedUserLoans(user).getResultList();
		
		//get finished user books
		Set<Item> userLoanedItems = new HashSet<Item>();
		for (Object k : userLoans){
			userLoanedItems.addAll(session.createQuery("from Item where itemId = " + ((Loan) k).getItemId() 
					+ " and repoId = " + repo.getRepoId()).list());
		}		
		
		Set<Book> userReadBooks = new HashSet<Book>();
		for (Item i: userLoanedItems){
			userReadBooks.add((Book) session.get(Book.class,  i.getBook().getIsbn()));
		}		

		
		session.getTransaction().commit();
		session.close();
		
		return userReadBooks;
	}
	
	/**
	 * Zwraca tytuly i autorow ksiazek, ktore sa proponowane do przeczytania
	 * uzytkownikowi
	 */
	public LogicRespond getSuggestions(User user){
		Set<Book> userReadBooks = new HashSet<Book>();
		repoBooks = new LinkedList<Object>();
		for (RepoUser ru : user.getRepoUsers()){
			
			userReadBooks.addAll(getReadBooks(user, ru.getRepo()));	
			if (RepoLogic.getInstance().getRepoBooks(ru.getRepo()).getResultList() != null){
				repoBooks.addAll( RepoLogic.getInstance().getRepoBooks(ru.getRepo()).getResultList());
			}			
		}
		Iterator<Object> it  = repoBooks.iterator();
		while (it.hasNext()){
			Object temp = it.next();
			for (Book b: userReadBooks){
				if (((Book) temp).getIsbn().compareTo(b.getIsbn()) == 0){
					it.remove();
				}
			}
		}
	
		
		connFactor = new Double[repoBooks.size()];
		for (int i=0; i<connFactor.length; ++i){
			connFactor[i] = new Double(0.0);
		}
		
		
		for (int rb = 0 ; rb < connFactor.length; rb++){ //rb - repo book counter
			String[] rbCategories = ((Book)repoBooks.get(rb)).getCategories().split(":");
			
			Double bookConnFactor = 0.;
		
			for (Book ub : userReadBooks){ //ub - userBookCounter
				
				String[] ubCategories = ub.getCategories().split(":");
				bookConnFactor = 0.;
				Double connFactorPart = 0.;
				
				for (int i=0; i<rbCategories.length; ++i){
					System.out.println(rbCategories[i]);
					
					for (int j=0; j < ubCategories.length; ++j){
						//System.out.println(rbCategories[i] + " " + ubCategories[j] + " "+ weights[categoryToNum.get(rbCategories[i])][categoryToNum.get(ubCategories[j])]);
						connFactorPart += Math.pow(weights[categoryToNum.get(rbCategories[i])][categoryToNum.get(ubCategories[j])],2);
					}
					
					bookConnFactor += Math.sqrt(connFactorPart);
					connFactorPart = 0.;
				}				
				if (ub.getAuthor().compareTo( ((Book)repoBooks.get(rb)).getAuthor()) != 0){
					bookConnFactor = bookConnFactor + 0.25;
				}
				int dayDiff = getDayDifference(user, ub);
				if (dayDiff > 0){
					bookConnFactor = bookConnFactor + 1/(Math.pow(dayDiff, 1/8));
				}
				connFactor[rb] += bookConnFactor;
			}			
		}		
		
		for (int i=0; i<connFactor.length; ++i){
			System.out.println(((Book)repoBooks.get(i)).getTitle() + "  " +connFactor[i]);
		}

		Collections.sort(repoBooks, new Comparator<Object>() {

			@Override
			public int compare(Object o1, Object o2) {
				if(connFactor[repoBooks.indexOf(o1)] < connFactor[repoBooks.indexOf(o2)]){
					return -1;
				}
				else if (connFactor[repoBooks.indexOf(o1)] > connFactor[repoBooks.indexOf(o2)]){
					return 1;
				}
				return 0;
			}			
		});
		LogicRespond suggestions = new LogicRespond("Suggestions", true);
		if (repoBooks.size() > 5){
			suggestions.setResultList(repoBooks.subList(0, 4));
		}	
		else {
			suggestions.setResultList(repoBooks);
		}
		
		return suggestions;
		
	}
	/**
	 * Oblicza ile dniu temu uzytkownik przeczytal dana ksiazke
	 */
	private Integer getDayDifference(User user, Book book){
		
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		
		List<Item> itemList = session.createQuery("from Item where isbn = '" + book.getIsbn() + "'").list();
	
		
		List<Loan> loanList = new LinkedList<Loan>();
		for (Item i : itemList){
			loanList.addAll(session.createQuery("from Loan where itemId = " + i.getItemId() + " and userId = " + user.getUserId()).list());
		}
		
		Collections.sort(loanList, new Comparator<Loan>() {
			@Override
			public int compare(Loan o1, Loan o2) {
				return o1.getLoanDate().compareTo(o2.getLoanDate());				
			}
			
		});
		Date loanDate = loanList.get(loanList.size() - 1).getReturnDate();
		
		java.util.Calendar cal = java.util.Calendar.getInstance();
		java.util.Date utilDate = cal.getTime();
		
		Integer dayDiff = utilDate.getDay() - loanDate.getDay();
		Integer monthDiff = (utilDate.getMonth() - loanDate.getMonth()) * 30;
		Integer yearDiff = (utilDate.getYear() - loanDate.getYear()) *365;
		
		
				
		session.getTransaction().commit();
		session.close();
		return dayDiff + monthDiff + yearDiff;
		
	}
	

}
